﻿
namespace Orchard.OData.Services
{
    using Microsoft.OData.Edm;
    using Microsoft.OData.Edm.Library;
    using Orchard.ContentManagement;
    using Orchard.ContentManagement.MetaData.Models;
    using Orchard.Core.Contents.Settings;
    using System.Collections.Generic;
    using System.Linq;

    public interface IMetadataBuilder : IDependency {
        IEdmModel BuildModel();
    }

    public sealed class MetadataBuilder : IMetadataBuilder {
        internal const string _namespace = "Orchard.OData";
        internal const string _qualifiedContainer = "DefaultContainer";

        private EdmModel _model;
        private readonly IContentManager _contentManager;
        public MetadataBuilder(IContentManager contentManager) {
            this._contentManager = contentManager;
        }

        private void BuildStructuralProperties(EdmStructuredType edmStructuredType, System.Type type) {
            type.GetProperties()
                .Where(prop => !prop.PropertyType.IsArray)
                .Where(prop => null == TypeSystem.FindIEnumerable(prop.PropertyType))
                .Select(prop => new {
                    Name = prop.Name,
                    EdmTypeReference = this._model.GetEdmTypeReference(prop.PropertyType)
                })
                .Where(prop => prop.EdmTypeReference != null)
                .Select(prop => edmStructuredType.AddStructuralProperty(prop.Name, prop.EdmTypeReference))
                .ToList();
        }

        private EdmComplexType BuildComplexType(System.Type contentPartType, string contentPartTypeName) {
            var foundType = this._model.FindType(MetadataBuilder._namespace + "." + contentPartTypeName);
            if (null != foundType) {
                return foundType as EdmComplexType;
            }

            var complexType = new EdmComplexType(_namespace, contentPartTypeName);
            this.BuildStructuralProperties(complexType, contentPartType);
            this._model.AddElement(complexType);
            return complexType;
        }

        private EdmComplexType BuildComplexType(ContentField contentField) {
            var complexType = this.BuildComplexType(contentField.GetType(), contentField.Name);
            this._model.SetAnnotationValue<ContentField>(complexType, contentField);

            // == Case like MediaLibraryPickerField
            //contentField
            //    .GetType()
            //    .GetProperties()
            //    .Where(property => property.PropertyType.IsGenericType)
            //    .Where(property => property.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            //    .Select(property => property.PropertyType.GenericTypeArguments.Single())
            //    .Where(orchardType => orchardType.BaseType.IsGenericType)
            //    .Where(orchardType => orchardType.BaseType.GetGenericTypeDefinition() == typeof(ContentPart<>))
            //    .Select(orchardType => (ContentPart)System.Activator.CreateInstance(orchardType))
            //    .Select(contentPart => {
            //        contentPart.TypePartDefinition = new ContentTypePartDefinition(new ContentPartDefinition(contentPart.GetType().Name), new SettingsDictionary());
            //        return contentPart;
            //    })
            //    .Select(orchardType => this.BuildComplexType(orchardType))
            //    .Select(edmStructuredType => new EdmComplexTypeReference(edmStructuredType, false))
            //    .Select(edmStructuredTypeReference => new EdmCollectionType(edmStructuredTypeReference))
            //    .Select(edmCollectionType => complexType.AddStructuralProperty(
            //        edmCollectionType.ElementType.Definition.FullTypeName().Split('.').LastOrDefault() + "s",
            //        new EdmCollectionTypeReference(edmCollectionType)))
            //    .ToList();

            return complexType;
        }

        private EdmComplexType BuildComplexType(ContentPart contentPart) {
            var complexType = this.BuildComplexType(contentPart.GetType(), contentPart.PartDefinition.Name);
            this._model.SetAnnotationValue<ContentPart>(complexType, contentPart);
         
            contentPart
                .Fields
                .Select(cpf => complexType.AddStructuralProperty(cpf.Name, new EdmComplexTypeReference(this.BuildComplexType(cpf), false)))
                .ToList();

            return complexType;
        }

        private EdmEntityType BuildEntityType(ContentTypeDefinition contentTypeDefinition) {
            var foundType = this._model.FindType(MetadataBuilder._namespace + "." + contentTypeDefinition.Name);
            if (null != foundType) {
                return foundType as EdmEntityType;
            }

            var edmEntityType = new EdmEntityType(_namespace, contentTypeDefinition.Name);
            edmEntityType.AddKeys(edmEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));

            var contentItem = this._contentManager.New(contentTypeDefinition.Name);
            this._model.SetAnnotationValue<ContentItem>(edmEntityType, contentItem);

            var contentParts = contentItem.Parts.Join(
                inner: contentItem.TypeDefinition.Parts,
                outerKeySelector: contentpart => contentpart.PartDefinition.Name,
                innerKeySelector: contentypepartdefinition => contentypepartdefinition.PartDefinition.Name,
                resultSelector: (contentpart, partdefinition) => contentpart);
            
            contentParts
                .Where(ctp => ctp.PartDefinition.Name.ToLower() != contentTypeDefinition.Name.ToLower())
                .Select(ctp => edmEntityType.AddStructuralProperty(ctp.PartDefinition.Name, new EdmComplexTypeReference(this.BuildComplexType(ctp), false)))
                .ToList();

            var fieldPart = contentParts
                .SingleOrDefault(ctp => ctp.PartDefinition.Name.ToLower() == contentTypeDefinition.Name.ToLower());
            if (null != fieldPart) {
                fieldPart
                   .Fields
                   .Select(cpf => edmEntityType.AddStructuralProperty(cpf.Name, new EdmComplexTypeReference(this.BuildComplexType(cpf), false)))
                   .ToList();
            }

            var _defaultContainer = this._model.EntityContainer as EdmEntityContainer;
            var resourceSet = _defaultContainer.AddEntitySet(edmEntityType.Name + "s", edmEntityType);
            this._model.AddElement(edmEntityType);

            return edmEntityType;
        }

        IEdmModel IMetadataBuilder.BuildModel() {
            this._model = new EdmModel();
            this._model.AddElement(new EdmEntityContainer(_namespace, _qualifiedContainer));
            this._contentManager
                .GetContentTypeDefinitions()
                .Where(ctd => ctd.Settings.GetModel<ContentTypeSettings>().Creatable)
                .Select(ctd => this.BuildEntityType(ctd))
                .ToList();
            return this._model;
        }
    }
}